#include "qrender/cone_renderer.h"

#include <GL/gl.h>

#include <QOpenGLContext>
#include <QOpenGLShaderProgram>
#include <QOpenGLVertexArrayObject>
#include <QOpenGLBuffer>
#include <QOpenGLFunctions>

#include <QtMath>

#include <qrender/camera.h>

ConeRenderer::ConeRenderer() : Renderer()
{
}

ConeRenderer::~ConeRenderer()
{
    ConeRenderer::release();
}

void ConeRenderer::setPosition(const QVector3D &pos)
{
    if (pos_ == pos) {
        return;
    }

    pos_ = pos;

    data_changed_ = true;
}

void ConeRenderer::setShape(const QVector3D &base, const QVector3D &apex,
                            float radius, int slices, const QVector4D &color)
{
    if (base_ == base && apex_ == apex && radius_ == radius &&
        slices_ == slices && color_ == color) {
        return;
    }

    base_ = base;
    apex_ = apex;
    radius_ = radius;
    slices_ = slices;
    color_ = color;

    data_changed_ = true;
}

void ConeRenderer::setColor(const QVector4D &color)
{
    if (color_ == color) {
        return;
    }

    color_ = color;
}

void ConeRenderer::create()
{
    // 顶点着色器
    const char *vsrc = R"(
#version 330

in vec3 v_pos;

uniform mat4 model_matrix;
uniform mat4 view_matrix;
uniform mat4 projection_matrix;

void main()
{
    gl_Position = projection_matrix * view_matrix * model_matrix * vec4(v_pos, 1.0);
}
)";
    // 片段着色器
    const char *fsrc = R"(
#version 330 core

uniform vec4 f_color;

out vec4 frag_color;

void main()
{
    frag_color = f_color;
}
)";

    // 创建着色器程序
    program_.reset(new QOpenGLShaderProgram());
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex,
                                               vsrc); // 顶点着色器
    program_->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment,
                                               fsrc); // 片段着色器
    program_->link();
    program_->bind();

    program_->setUniformValue("view_matrix", QMatrix4x4());
    program_->setUniformValue("projection_matrix", QMatrix4x4());
    program_->setUniformValue("f_color", color_);

    vao_.reset(new QOpenGLVertexArrayObject());
    vao_->create();

    // 生成顶点数据
    generateConeVertices(base_, apex_, radius_, slices_);
    vbo_.reset(new QOpenGLBuffer());
    vbo_->create();
    vbo_->bind();

    {
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());

        // 告知 OpenGL 如何解析数据, 数据解析格式
        GLuint v_pos = program_->attributeLocation("v_pos");
        program_->setAttributeBuffer(v_pos, GL_FLOAT, 0, 3,
                                     3 * sizeof(GLfloat));
        program_->enableAttributeArray(v_pos);
    }

    vbo_->release();
    program_->release();
}

void ConeRenderer::render(const CameraInfo &camera)
{
    // 透明不渲染
    // TODO: 有透明度在后面渲染
    if (color_[3] == 0) {
        return;
    }

    // 更新 OpenGL 配置
    auto f = QOpenGLContext::currentContext()->functions();
    f->glEnable(GL_DEPTH_TEST);
    f->glEnable(GL_POLYGON_SMOOTH);
    f->glDisable(GL_CULL_FACE); // 禁用背面剔除

    // 绑定着色器和缓冲区到当前上下文
    program_->bind();
    vbo_->bind();

    // 相机参数
    if (camera.valid) {
        program_->setUniformValue("view_matrix", camera.view_matrix);
        program_->setUniformValue("projection_matrix",
                                  camera.projection_matrix);
    }

    QMatrix4x4 model_matrix;
    model_matrix.translate(pos_);
    program_->setUniformValue("model_matrix", model_matrix);
    program_->setUniformValue("f_color", color_);

    // 模型数据
    if (data_changed_) {
        generateConeVertices(base_, apex_, radius_, slices_);

        data_changed_ = false;
    }

    {
        vbo_->allocate(vertices_.constData(),
                       vertices_.size() * sizeof(QVector3D));
        QOpenGLVertexArrayObject::Binder vao_binder(vao_.get());
        f->glDrawElements(GL_TRIANGLE_FAN, base_indices_.size(),
                          GL_UNSIGNED_INT, base_indices_.constData());
        f->glDrawElements(GL_TRIANGLE_FAN, side_indices_.size(),
                          GL_UNSIGNED_INT, side_indices_.constData());
    }

    vbo_->release();
    program_->release();
}

void ConeRenderer::release()
{
    vao_.reset();
    vbo_.reset();
    program_.reset();
}

void ConeRenderer::generateConeVertices(const QVector3D &base,
                                        const QVector3D &apex, float radius,
                                        int slices)
{
    // 清空输入向量
    vertices_.clear();
    base_indices_.clear();
    side_indices_.clear();

    const float angle_inc = 2.0f * M_PI / slices;

    // 圆锥底部圆心到顶点单位向量 高度向量 h
    QVector3D h = (apex - base).normalized();

    // 构建与 high_dir 垂直的向量 底部平面向量1  u
    QVector3D u = QVector3D(1.0f, 0.0f, 0.0f); // 假设起始向量为X轴
    if (qAbs(QVector3D::dotProduct(u, h)) > 0.99f) {
        u = QVector3D(0.0f, 1.0f, 0.0f); // 如果X轴与axis共线，换为Y轴
    }
    u = QVector3D::crossProduct(u, h);

    // 构建与前两者垂直的向量 底部平面向量2 r
    QVector3D r = QVector3D::crossProduct(h, u).normalized();

    // 记录底部圆心, 与圆周组合使用 GL_TRIANGLE_FAN 方式画圆, 即底座
    vertices_ << base;

    // 生成锥体底部圆周顶点数据
    for (int i = 0; i <= slices; ++i) {
        float angle = angle_inc * i;

        QVector3D v = base + radius * (cos(angle) * u + sin(angle) * r);

        vertices_ << v;
    }

    // 生成锥体顶点
    vertices_ << apex;

    // 生成圆锥底面顶点索引
    base_indices_ << 0;
    for (int i = 1; i <= slices + 1; i++) {
        base_indices_ << i;
    }

    // 生成圆锥侧面顶点索引
    side_indices_ << slices + 2;
    for (int i = 1; i <= slices + 1; i++) {
        side_indices_ << i;
    }
}
